package cn.pso.nspsofs;

import cn.knn.TestKNN;
import cn.read.ReadFile;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.web.client.RestTemplate;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static java.util.stream.Collectors.toList;

public enum FN {
    FILE("") {
        @Override
        public double[] doCalcFitness(List<Object> features) throws IOException {
            ReadFile rf = new ReadFile();
            rf.getFile(features, "dataset\\Alltra--" + Process_NSPSOFS.name, "tra");
            rf.getFile(features, "dataset\\Alltest--" + Process_NSPSOFS.name, "test");

            double accuracy = new TestKNN().runKnn("tra", "test");
            return new double[]{features.size(), 1 - accuracy};
        }

        @Override
        public double[][] doGetBounds(int featureSize) {
            return new double[][]{{0, 1}};
        }

        @Override
        public List<Object> getFeatures(double[] pos) {
            List<Object> features = new ArrayList<>();
            int j = 1;
            for (double po : pos) {
                if (po > 0.6) {
                    // 记录被选中的特征位置
                    features.add(String.valueOf(j));
                }
                j++;
            }
            return features;
        }
    }, DTLZ1("n10000\\DTLZ1"), DTLZ2("n10000\\DTLZ2"), DTLZ3("n10000\\DTLZ3"), DTLZ4("n10000\\DTLZ4"), DTLZ5("n10000\\DTLZ5"), DTLZ6("n10000\\DTLZ6"), DTLZ7("n10000\\DTLZ7"),
    WFG1("wfg-pf\\WFG1.3D.pf"), WFG2("wfg-pf\\WFG2.3D.pf"), WFG3("wfg-pf\\WFG3.3D.pf"), WFG4("wfg-pf\\WFG4.3D.pf"), WFG5("wfg-pf\\WFG5.3D.pf"), WFG6("wfg-pf\\WFG6.3D.pf"), WFG7("wfg-pf\\WFG7.3D.pf"), WFG8("wfg-pf\\WFG8.3D.pf"), WFG9("wfg-pf\\WFG9.3D.pf");

    private final Logger log = LoggerFactory.getLogger(FN.class);
    private final String serverUrl = "http://localhost:6008";
    private final String pfPath;
    private List<double[]> pf;


    FN(String pfPath) {
        this.pfPath = pfPath;
    }

    public double[] doCalcFitness(List<Object> features) throws IOException {
        String name = this.name();
        JSONObject param = new JSONObject();
        param.put("pname", name);
        param.put("x", features);
        param.put("n_var", features.size());
        param.put("n_obj", 3);

        RestTemplate restTemplate = RequestHolder.getInstance();
        JSONObject response = restTemplate.postForObject(serverUrl + "/get_fitness", param, JSONObject.class);
        if (response == null) {
            throw new RuntimeException("fitness server error");
        }
        // {'result': {'fitness': tuple }}
        JSONObject result = response.getJSONObject("result");
        JSONArray fitness = result.getJSONArray("fitness");

        if (fitness == null) {
            log.error("response: {}", response);
            throw new RuntimeException("fitness server error");
        }
        double[] calculatedFitness = new double[fitness.size()];
        for (int i = 0; i < fitness.size(); i++) {
            calculatedFitness[i] = fitness.getDoubleValue(i);
        }
        return calculatedFitness;
    }

    public double[][] doGetBounds(int featureSize) {
        String name = this.name();
        JSONObject param = new JSONObject();
        param.put("pname", name);
        param.put("n_var", featureSize);

        RestTemplate restTemplate = RequestHolder.getInstance();
        JSONObject response = restTemplate.postForObject(serverUrl + "/get_boundary", param, JSONObject.class);
        if (response == null || !Boolean.TRUE.equals(response.getBooleanValue("success"))) {
            throw new RuntimeException("bounds server error");
        }
        // {'result': {'fitness': tuple }}
        // {"result":{"lbound":[0,0,0],"rbound":[1,1,1]},"code":0,"success":true}
        JSONObject result = response.getJSONObject("result");
        List<Double> lbounds = Optional.ofNullable(result.getJSONArray("lbound")).map(p -> p.toJavaList(Double.class)).orElse(null);
        List<Double> rbounds = Optional.ofNullable(result.getJSONArray("rbound")).map(p -> p.toJavaList(Double.class)).orElse(null);

        if (CollectionUtils.isEmpty(lbounds) || CollectionUtils.isEmpty(rbounds) || lbounds.size() != rbounds.size()) {
            log.error("response: {}", response);
            throw new RuntimeException("bounds server error or lbound size not equals rbound size");
        }

        double[][] bounds = new double[featureSize][2];
        for (int i = 0; i < bounds.length; i++) {
            bounds[i] = new double[]{lbounds.get(i), rbounds.get(i)};
        }

        return bounds;
    }

    public double doCalcIgd(List<double[]> fitnessList, List<double[]> realPf) {
        JSONObject param = new JSONObject();
        param.put("pf", fitnessList);
        param.put("points", realPf);

        RestTemplate restTemplate = RequestHolder.getInstance();
        JSONObject response = restTemplate.postForObject(serverUrl + "/calc_igd", param, JSONObject.class);
        if (response == null || !Boolean.TRUE.equals(response.getBooleanValue("success"))) {
            throw new RuntimeException("bounds server error");
        }
        // {'code': 0, 'success': True, 'result': {'igd': igd}}
        JSONObject result = response.getJSONObject("result");
        if (!result.containsKey("igd")) {
            log.error("response: {}", response);
            throw new RuntimeException("igd server error");
        }

        return result.getDoubleValue("igd");
    }

    public List<Object> getFeatures(double[] pos) {
        return Arrays.stream(pos).boxed().collect(toList());
    }

    public List<double[]> readRealPf() {
        List<double[]> result = pf;
        if (result == null) {
            result = new ArrayList<>(2000);
            try {
                BufferedReader bufferedReader = new BufferedReader(new FileReader(ResourceUtils.getFile("classpath:" + pfPath)));
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    String[] s = line.split(" ");
                    double[] realPf = Arrays.stream(s).mapToDouble(Double::parseDouble).toArray();
                    result.add(realPf);
                }
                pf = result;
            } catch (FileNotFoundException e) {
                log.error("file not found, filename: {}", pfPath);
            } catch (IOException e) {
                log.error(e.getMessage());
            }
        }
        return result;
    }
}
